import type { Object3D } from 'three';

import type { Editor } from '../Editor.ts';
import { type BaseCommandJsonType, Command } from './Command.ts';

type MoveObjectCommandJsonType = BaseCommandJsonType & {
  objectUuid: string;
  newParentUuid: string;
  oldParentUuid: string;
  newIndex: number;
  oldIndex: number;
};

class MoveObjectCommand extends Command {
  public oldParent: Object3D;
  oldIndex: number;
  newParent: Object3D;
  newIndex: number;
  newBefore: Object3D | null;
  object: Object3D;

  constructor(editor: Editor, object: Object3D, newParent: Object3D, newBefore: Object3D | null = null) {
    super(editor);

    this.type = 'MoveObjectCommand';
    this.name = `command/MoveObject: ${object.name}`;
    this.object = object;
    this.oldParent = object.parent as Object3D;
    this.oldIndex = this.oldParent.children.indexOf(this.object);
    this.newParent = newParent;

    if (newBefore !== null) {
      this.newIndex = newParent.children.indexOf(newBefore);
    } else {
      this.newIndex = newParent.children.length;
    }

    if (this.oldParent === this.newParent && this.newIndex > this.oldIndex) {
      this.newIndex--;
    }

    this.newBefore = newBefore;
  }

  execute() {
    this.oldParent.remove(this.object);

    const children = this.newParent.children;
    children.splice(this.newIndex, 0, this.object);
    this.object.parent = this.newParent;

    this.object.dispatchEvent({ type: 'added' });
    this.editor.signals.objectChanged.dispatch(this.object);
    this.editor.signals.objectChanged.dispatch(this.newParent);
    this.editor.signals.objectChanged.dispatch(this.oldParent);
    this.editor.signals.sceneGraphChanged.dispatch();
  }

  undo() {
    this.newParent.remove(this.object);

    const children = this.oldParent.children;
    children.splice(this.oldIndex, 0, this.object);
    this.object.parent = this.oldParent;

    this.object.dispatchEvent({ type: 'added' });
    this.editor.signals.objectChanged.dispatch(this.object);
    this.editor.signals.objectChanged.dispatch(this.newParent);
    this.editor.signals.objectChanged.dispatch(this.oldParent);
    this.editor.signals.sceneGraphChanged.dispatch();
  }

  toJSON(): MoveObjectCommandJsonType {
    return {
      ...super.toJSON(),
      objectUuid: this.object.uuid,
      newParentUuid: this.newParent.uuid,
      oldParentUuid: this.oldParent.uuid,
      newIndex: this.newIndex,
      oldIndex: this.oldIndex,
    };
  }

  fromJSON(json: MoveObjectCommandJsonType) {
    super.fromJSON(json);

    this.object = this.editor.objects.get(json.objectUuid) as Object3D;

    if (!this.editor.objects.has(json.oldParentUuid)) {
      this.oldParent = this.editor.scene;
    }

    if (!this.editor.objects.has(json.newParentUuid)) {
      this.newParent = this.editor.scene;
    }

    this.newIndex = json.newIndex;
    this.oldIndex = json.oldIndex;
  }
}

export { MoveObjectCommand };
